// Maps and tiles
//--------------------------------
#ifndef MAP_H
#define MAP_H
#include "common.h"
#include "game.h"
#include "actor.h"

class Tile {
public:

    enum Type {
        FLOOR,
        WALL,
        ROCK,
        WATER,
        LAVA
    };

    Tile();
    virtual ~Tile();

    // Tile type
    uint GetType() const;
    void SetType( uint type );
    std::string GetName( const Map& map ) const;

    // Memory - what the game remembers was here. Might not be what
    // is actually here
    void               Memorise( const Map& map, uint x, uint y, bool itemsInDark = false );
    const IO::SymList& GetSymbol() const;

    // Active actors
    friend class Actor;
    bool IsEmpty() const;
    bool IsActorOnTile( const Actor* actor ) const;
    void GetActorsOnTile(      ActorList& outputList );
    void GetActorsOnTile( ConstActorList& outputList ) const;
    template< class BehaviourInterface >
    void GetActorsOnTile(      ActorList& outputList );
    template< class BehaviourInterface >
    void GetActorsOnTile( ConstActorList& outputList ) const;

    bool IsBlocked() const;
    bool IsBlockedByTerrain() const;
    bool IsBlockedByActor() const;
    bool IsLOSBlocked() const;

private:

    void Add   ( Actor* actor );
    void Remove( Actor* actor );

    uint        _type;
    IO::SymList _tileSymbol;
    IO::SymList _actorSymbol;
    ActorReference _memoryActor;

    ActorSet _actors;

};

class Map {
public:

    Map( Game& game, uint width, uint height );
    virtual ~Map();

    SyncIO GetSyncIO()  const;
    IO&    GetAsyncIO() const;
          Game& GetGame();
    const Game& GetGame() const;

          Tile& GetTile( uint x, uint y );
    const Tile& GetTile( uint x, uint y ) const;

    uint GetWidth() const;
    uint GetHeight() const;

    void DeleteAll();

    // Type - affects tile graphics (aesthetic only)
    enum Type {
        DUNGEON,
        CAVERN,
        BARRENS,
        PLANE
    };

    uint GetType() const;
    void SetType( uint type );
    bool IsOutsideMap() const;
    bool IsWithinMap( int x, int y ) const;

    // Find an empty tile
    void GetFreeTile( int& x, int& y, bool blockable = false );

    // Active actors
    friend class Actor;
    bool IsActorOnMap( const Actor* actor ) const;
    void GetActorsOnMap(      ActorList& outputList );
    void GetActorsOnMap( ConstActorList& outputList ) const;
    template< class BehaviourInterface >
    void GetActorsOnMap(      ActorList& outputList );
    template< class BehaviourInterface >
    void GetActorsOnMap( ConstActorList& outputList ) const;

    bool IsInLOS    ( int ax, int ay, int bx, int by ) const;
    uint GetDistance( int ax, int ay, int bx, int by ) const;
    double GetLightValue( int x, int y ) const;
    double GetCalculatedLightValue( int x, int y ) const;
    void   CalculateLightValues( int left, int top, int right, int bottom );

    void Tick();

private:

    void Add               ( Actor* actor, int time );
    int  Remove            ( Actor* actor );
    // True if adding an object here will block a corridor
    bool IsTileBlockable( int x, int y ) const;

    Game&    _game;
    TileList _tiles;
    std::vector< double > _lightValues;

    uint _width;
    uint _height;
    uint _type;

    struct MapActor {
        MapActor( Actor* actor, int time )
        : a( actor ), t( time ) { }

        Actor* a;
        mutable int t;
    };
    class MapActorSorter {
    public:
        bool operator()( const Map::MapActor& less, const Map::MapActor& greater ) const;
    };
    typedef std::set< MapActor, MapActorSorter > MapActorSet;
    MapActorSet _actors;

};

template< class BehaviourInterface >
void Tile::GetActorsOnTile( ActorList& outputList )
{
    foreach ( Actor* a, _actors ) {
        if ( a->Has< BehaviourInterface >() )
            outputList.push_back( a );
    }
    std::sort( outputList.begin(), outputList.end(), &Actor::PrioritySort );
}

template< class BehaviourInterface >
void Tile::GetActorsOnTile( ConstActorList& outputList ) const
{
    foreach ( const Actor* a, _actors ) {
        if ( a->Has< BehaviourInterface >() )
            outputList.push_back( a );
    }
    std::sort( outputList.begin(), outputList.end(), &Actor::PrioritySort );
}

template< class BehaviourInterface >
void Map::GetActorsOnMap( ActorList& outputList )
{
    foreach ( MapActor& a, _actors ) {
        if ( a.a->Has< BehaviourInterface >() )
            outputList.push_back( a.a );
    }
    std::sort( outputList.begin(), outputList.end(), &Actor::PrioritySort );
}

template< class BehaviourInterface >
void Map::GetActorsOnMap( ConstActorList& outputList ) const
{
    foreach ( const MapActor& a, _actors ) {
        if ( a.a->Has< BehaviourInterface >() )
            outputList.push_back( a.a );
    }
    std::sort( outputList.begin(), outputList.end(), &Actor::PrioritySort );
}

#endif
